import {AxesHelper, Bone, BoxHelper, BufferGeometry, Camera, CameraHelper, DirectionalLight, DirectionalLightHelper, Float32BufferAttribute, Geometry, HemisphereLight, HemisphereLightHelper, Light, LightProbe, Line, LineBasicMaterial, Material, Mesh, MeshStandardMaterial, Object3D, Plane, PointLight, PointLightHelper, Points, PointsMaterial, Raycaster, RectAreaLight, Scene, ShaderMaterial, SkinnedMesh, SpotLight, SpotLightHelper, Sprite, SpriteMaterial, Texture, Vector2, Vector3} from "three";
import {ActionBundle} from "../../../history/action/ActionBundle.js";
import {AddResourceAction} from "../../../history/action/resources/AddResourceAction.js";
import {Audio} from "../../../../core/resources/Audio.js";
import {AudioEmitter} from "../../../../core/objects/audio/AudioEmitter.js";
import {ButtonIcon} from "../../../components/buttons/ButtonIcon.js";
import {ChangeAction} from "../../../history/action/ChangeAction.js";
import {Component} from "../../../components/Component.js";
import {CubeCamera} from "../../../../core/objects/cameras/CubeCamera.js";
import {CubeTexture} from "../../../../core/texture/CubeTexture.js";
import {DragBuffer} from "../../DragBuffer.js";
import {DropdownList} from "../../../components/input/DropdownList.js";
import {Editor} from "../../../Editor.js";
import {EventManager} from "../../../../core/utils/EventManager.js";
import {Font} from "../../../../core/resources/Font.js";
import {Global} from "../../../Global.js";
import {Group} from "../../../../core/objects/misc/Group.js";
import {Image} from "../../../../core/resources/Image.js";
import {Keyboard} from "../../../../core/input/Keyboard.js";
import {LensFlare} from "../../../../core/objects/misc/LensFlare.js";
import {Loaders} from "../../../Loaders.js";
import {Locale} from "../../../locale/LocaleManager.js";
import {Model} from "../../../../core/resources/Model.js";
import {Mouse} from "../../../../core/input/Mouse.js";
import {Nunu} from "../../../../core/Nunu.js";
import {ObjectIcons} from "../../../utils/ObjectIcons.js";
import {OrthographicCamera} from "../../../../core/objects/cameras/OrthographicCamera.js";
import {PerspectiveCamera} from "../../../../core/objects/cameras/PerspectiveCamera.js";
import {PhysicsObject} from "../../../../core/objects/physics/PhysicsObject.js";
import {Measurement} from "../../../../core/objects/misc/Measurement.js";
import {RendererCanvas} from "../../../components/RendererCanvas.js";
import {Settings} from "../../../Settings.js";
import {SpineAnimation} from "../../../../core/objects/spine/SpineAnimation.js";
import {SwapAction} from "../../../history/action/objects/SwapAction.js";
import {TabComponent} from "../../../components/tabs/TabComponent.js";
import {Video} from "../../../../core/resources/Video.js";
import {VideoTexture} from "../../../../core/texture/VideoTexture.js";
import {Viewport} from "../../../../core/objects/cameras/Viewport.js";
import {TransformControls} from "./transform/TransformControls.js";
import {ToolBar} from "./toolbar/ToolBar.js";
import {SkeletonHelper} from "./helpers/SkeletonHelper.js";
import {SideBar} from "./sidebar/SideBar.js";
import {RectAreaLightHelper} from "./helpers/RectAreaLightHelper.js";
import {PointsHelper} from "./helpers/PointsHelper.js";
import {PhysicsObjectHelper} from "./helpers/PhysicsObjectHelper.js";
import {OrientationCube} from "./utils/OrientationCube.js";
import {ObjectIconHelper} from "./helpers/ObjectIconHelper.js";
import {LineHelper} from "./helpers/LineHelper.js";
import {LightProbeHelper} from "./helpers/LightProbeHelper.js";
import {GridHelper} from "./helpers/GridHelper.js";
import {EditorPlanarControls} from "./controls/EditorPlanarControls.js";
import {EditorOrbitControls} from "./controls/EditorOrbitControls.js";
import {EditorFreeControls} from "./controls/EditorFreeControls.js";
import {WireframeHelper} from "./helpers/WireframeHelper.js";

/** 
 * The scene editor is the core of the nunuStudio editor.
 *
 * It is used to edit the scenes and apply changes to the objects using helper objects.
 *
 * @class SceneEditor
 * @extends {TabComponent}
 */
function SceneEditor(parent, closeable, container, index)
{
	TabComponent.call(this, parent, closeable, container, index, Locale.scene, Global.FILE_PATH + "icons/misc/scene.png");

	var self = this;

	/**
	 * Rendering canvas element where the scene is presented.
	 *
	 * @attribute canvas
	 * @type {RendererCanvas}
	 */
	this.canvas = new RendererCanvas(this, Editor.getRendererConfig());
	this.canvas.onResize = function(width, height)
	{
		if (self.scene !== null)
		{
			self.scene.resize(width, height);
		}
	};
	this.canvas.resetCanvas = function()
	{
		RendererCanvas.prototype.resetCanvas.call(this);

		self.transform.setCanvas(this.canvas);
		self.mouse.setCanvas(this.canvas);

		this.canvas.ondragover = Component.preventDefault;
		this.canvas.ondrop = function(event)
		{
			event.preventDefault();

			var uuid = event.dataTransfer.getData("uuid");
			var draggedObject = DragBuffer.get(uuid);

			var canvas = this;
			var rect = canvas.getBoundingClientRect();

			var position = new Vector2(event.clientX - rect.left, event.clientY - rect.top);
			var normalized = new Vector2(position.x / self.canvas.size.x * 2.0 - 1.0, -2.0 * position.y / self.canvas.size.y + 1.0);
			self.raycaster.setFromCamera(normalized, self.camera);

			var intersections = self.raycaster.intersectObjects(self.scene.children, true);

			// Auxiliar method to copy details from a object to a destination
			function copyDetails(destination, object)
			{
				destination.name = object.name;
				destination.visible = object.visible;
				destination.castShadow = object.castShadow;
				destination.receiveShadow = object.receiveShadow;
				destination.frustumCulled = object.frustumCulled;
				destination.renderOrder = object.renderOrder;
				destination.matrixAutoUpdate = object.matrixAutoUpdate;
				destination.position.copy(object.position);
				destination.scale.copy(object.scale);
				destination.quaternion.copy(object.quaternion);
			}

			// Auxiliar method to attach textures to objects
			function attachTexture(texture, object)
			{
				var material = null;
				if (object instanceof Mesh || object instanceof SkinnedMesh)
				{
					material = new MeshStandardMaterial({map: texture, color: 0xFFFFFF, roughness: 0.6, metalness: 0.2});
					material.name = texture.name;
				}
				else if (object instanceof Line)
				{
					material = new LineBasicMaterial({color: 0xFFFFFF});
					material.name = texture.name;
				}
				else if (object instanceof Points)
				{
					material = new PointsMaterial({map: texture, color: 0xFFFFFF});
					material.name = texture.name;
				}
				else if (object instanceof Sprite)
				{
					material = new SpriteMaterial({map: texture, color: 0xFFFFFF});
					material.name = texture.name;
				}

				Editor.addAction(new ActionBundle(
					[
						new AddResourceAction(material, Editor.program, "materials"), 
						new ChangeAction(object, "material", material)
					]));
			}

			// Dragged resource
			if (draggedObject !== null)
			{
				// Object intersected
				if (intersections.length > 0)
				{
					var object = intersections[0].object;

					// Material
					if (draggedObject instanceof Material)
					{
						// Sprite material
						if (draggedObject instanceof SpriteMaterial)
						{
							if (object instanceof Sprite)
							{
								Editor.addAction(new ChangeAction(object, "material", draggedObject));
							}
						}
						// Points material
						else if (draggedObject instanceof PointsMaterial)
						{
							if (object instanceof Points)
							{
								Editor.addAction(new ChangeAction(object, "material", draggedObject));
							}
							else if (object.geometry !== undefined)
							{
								var newObject = new Points(object.geometry, draggedObject);
								copyDetails(newObject, object);
								Editor.addAction(new SwapAction(object, newObject, true));
							}
						}
						// Line material
						else if (draggedObject instanceof LineBasicMaterial)
						{
							if (object instanceof Line)
							{
								Editor.addAction(new ChangeAction(object, "material", draggedObject));
							}
							else if (object.geometry !== undefined)
							{
								var newObject = new Line(object.geometry, draggedObject);
								copyDetails(newObject, object);
								Editor.addAction(new SwapAction(object, newObject, true));
							}
						}
						// Shader material
						else if (draggedObject instanceof ShaderMaterial)
						{
							if (object.material !== undefined)
							{
								Editor.addAction(new ChangeAction(object, "material", draggedObject));
							}
						}
						// Mesh material
						else
						{
							if (object instanceof Mesh)
							{
								Editor.addAction(new ChangeAction(object, "material", draggedObject));
							}
							else if (object.geometry !== undefined)
							{
								var newObject = new Mesh(object.geometry, draggedObject);
								copyDetails(newObject, object);
								Editor.addAction(new SwapAction(object, newObject, true));
							}
						}
					}
					// Cubemap
					else if (draggedObject.isCubeTexture === true)
					{
						if (object.material instanceof Material)
						{
							Editor.addAction(new ChangeAction(object.material, "envMap", draggedObject));
							self.canvas.reloadContext();
						}
					}
					// Texture
					else if (draggedObject instanceof Texture)
					{
						attachTexture(draggedObject, object);
					}
					// Image
					else if (draggedObject instanceof Image)
					{
						attachTexture(new Texture(draggedObject), object);
					}
					// Video
					else if (draggedObject instanceof Video)
					{
						attachTexture(new VideoTexture(draggedObject), object);
					}
					// Font
					else if (draggedObject instanceof Font)
					{
						if (object.font !== undefined)
						{
							object.setFont(draggedObject);
							Editor.updateObjectsViewsGUI();
						}
					}
					// Geometry
					else if (draggedObject instanceof Geometry || draggedObject instanceof BufferGeometry)
					{
						if (object instanceof Mesh || object instanceof Points || object instanceof Line)
						{
							Editor.addAction(new ChangeAction(object, "geometry", draggedObject));
						}
					}
				}

				// Create audio emitter
				if (draggedObject instanceof Audio)
				{
					var audio = new AudioEmitter(draggedObject);
					audio.name = draggedObject.name;
					Editor.addObject(audio);
				}
			}
			// Dragged file
			else if (event.dataTransfer.files.length > 0)
			{
				var files = event.dataTransfer.files;

				for (var i = 0; i < files.length; i++)
				{
					var file = files[i];

					// Check if mouse intersects and object
					if (intersections.length > 0)
					{
						var object = intersections[0].object;

						// Image
						if (Image.fileIsImage(file))
						{
							Loaders.loadTexture(file, function(texture)
							{
								attachTexture(texture, object);
							});
						}
						// Video
						else if (Video.fileIsVideo(file))
						{
							Loaders.loadVideoTexture(file, function(texture)
							{
								attachTexture(texture, object);
							});
						}
						// Font
						else if (Font.fileIsFont(file))
						{
							if (object.font !== undefined)
							{
								Loaders.loadFont(file, function(font)
								{
									object.setFont(font);
								});
							}
						}
					}
					
					// Model
					if (Model.fileIsModel(file))
					{
						Loaders.loadModel(file);
					}
				}
			}

		};
	};

	/**
	 * Keyboard input object.
	 *
	 * @attribute keyboard
	 * @type {Keyboard}
	 */
	this.keyboard = new Keyboard();

	/** 
	 * Mouse input object
	 *
	 * It is attached to the window object to capture movement outside of the tab division.
	 *
	 * @attribute mouse
	 * @type {Mouse}
	 */
	this.mouse = new Mouse(window, true);

	/** 
	 * Raycaster object used for object picking.
	 *
	 * @attribute raycaster
	 * @type {Raycaster}
	 */
	this.raycaster = new Raycaster();

	/**
	 * Normalized mouse coordinates for raycasting.
	 *
	 * @attribute normalized
	 * @type {Vector2}
	 */
	this.normalized = new Vector2();

	/**
	 * Scene being edited in this tab.
	 *
	 * Can also be a regular 3D object.
	 *
	 * @attribute scene
	 * @type {Object3D}
	 */
	this.scene = null;

	/** 
	 * The tool bar contains the selector for the transform tools and object placing icons.
	 *
	 * @attribute sideBar
	 * @type {SideBar}
	 */
	this.sideBar = new SideBar(this);
	
	/**
	 * Camera orientation cube.
	 *
	 * Used to preview the orientation of the editor camera.
	 *
	 * @attribute orientation
	 * @type {OrientationCube}
	 */
	this.orientation = new OrientationCube();

	/**
	 * Helper scene stored the object and editor preview objects.
	 *
	 * @attribute helperScene
	 * @type {Scene}
	 */
	this.helperScene = new Scene();
	this.helperScene.matrixAutoUpdate = false;

	/**
	 * Grid helper configured to match editor settings.
	 *
	 * @attribute gridHelper
	 * @type {GridHelper}
	 */
	this.gridHelper = new GridHelper(Editor.settings.editor.gridSize, Editor.settings.editor.gridSpacing, 0x888888);
	this.gridHelper.visible = Editor.settings.editor.gridEnabled;
	this.helperScene.add(this.gridHelper);

	/**
	 * Axes helper configured to match editor settings.
	 *
	 * @attribute axisHelper
	 * @type {AxesHelper}
	 */
	this.axisHelper = new AxesHelper(Editor.settings.editor.gridSize);
	this.axisHelper.material.depthWrite = false;
	this.axisHelper.material.transparent = true;
	this.axisHelper.material.opacity = 1.0;
	this.axisHelper.visible = Editor.settings.editor.axisEnabled;
	this.helperScene.add(this.axisHelper);

	/**
	 * Object helper container.
	 *
	 * @attribute objectHelper
	 * @type {Group}
	 */
	this.objectHelper = new Group();
	this.objectHelper.matrixAutoUpdate = true;
	this.helperScene.add(this.objectHelper);

	/**
	 * Temporary preview line while selecting measurement points.
	 *
	 * @attribute measurementPreview
	 * @type {Line}
	 */
	var measurementPreviewGeometry = new BufferGeometry();
	measurementPreviewGeometry.setAttribute("position", new Float32BufferAttribute([0, 0, 0, 0, 0, 0], 3));
	var measurementPreviewMaterial = new LineBasicMaterial({color: 0xFFFF00, depthTest: false, depthWrite: false, transparent: true, opacity: 0.75});
	this.measurementPreview = new Line(measurementPreviewGeometry, measurementPreviewMaterial);
	this.measurementPreview.visible = false;
	this.measurementPreview.frustumCulled = false;
	this.measurementPreview.matrixAutoUpdate = false;
	this.measurementPreview.matrix.identity();
	this.measurementPreview.matrixWorld.identity();
	this.helperScene.add(this.measurementPreview);

	/**
	 * Measurement points collected while using the measurement tool.
	 *
	 * @attribute measurementPoints
	 * @type {Array}
	 */
	this.measurementPoints = [];

	/**
	 * Plane used to pick measurement points when nothing is intersected.
	 *
	 * @attribute measurementPlane
	 * @type {Plane}
	 */
	this.measurementPlane = new Plane(new Vector3(0, 1, 0), 0);

	/**
	 * Temporary vector reused when calculating measurement intersections.
	 *
	 * @attribute measurementIntersection
	 * @type {Vector3}
	 */
	this.measurementIntersection = new Vector3();

	/**
	 * Group where the object manipulation tools are drawn
	 *
	 * @attribute toolScene
	 * @type {Scene}
	 */
	this.toolScene = new Scene();
	this.toolScene.matrixAutoUpdate = false;

	/**
	 * Editor manipulation mode.
	 *
	 * @attribute mode
	 * @type {number}
	 */
	this.mode = SceneEditor.SELECT;

	/** 
	 * Transform controls tool.
	 *
	 * @attribute transform
	 * @type {TransformControls}
	 */
	this.transform = new TransformControls(this.camera, null, this.mouse);
	this.transform.visible = false;
	this.toolScene.add(this.transform);

	/**
	 * Camera object used to visualize the scene.
	 *
	 * This object is attached to the scene as the defaultCamera, allowing it to be used for runtime when there is no default camera.
	 *
	 * Can be a an OrthographicCamera or PerspectiveCamera dependeing on the cameraMode value.
	 *
	 * @attribute camera
	 * @type {Camera}
	 */
	this.camera = null;
	
	/** 
	 * Camera controls object used to manipulate the camera position.
	 *
	 * Can be EditorFreeControls, EditorOrbitControls or EditorPlanarControls.
	 *
	 * @attribute controls
	 * @type {Group}
	 */
	this.controls = null;
	this.controlsMode = -1;
	this.setCameraMode(SceneEditor.PERSPECTIVE);

	/**
	 * Transformation controls mode can be local or world.
	 *
	 * @attribute transformationSpace
	 * @type {DropdownList}
	 */
	this.transformationSpace = new DropdownList(this);
	this.transformationSpace.size.set(60, 30);
	this.transformationSpace.position.set(145, 5);
	this.transformationSpace.updatePosition(Component.BOTTOM_RIGHT);
	this.transformationSpace.updateSize();
	this.transformationSpace.addValue(Locale.local, TransformControls.LOCAL);
	this.transformationSpace.addValue(Locale.world, TransformControls.WORLD);
	this.transformationSpace.element.style.opacity = 0.5;
	this.transformationSpace.setOnChange(function()
	{
		var space = self.transformationSpace.getValue();
		Editor.settings.editor.transformationSpace = space;
		self.transform.space = space;
	});
	this.transformationSpace.element.onmouseenter = function()
	{
		this.style.opacity = 1.0;
	};
	this.transformationSpace.element.onmouseleave = function()
	{
		this.style.opacity = 0.5;
	};

	/**
	 * Dropdown to select the world navigation mode to use.
	 *
	 * @attribute navigation
	 * @type {DropdownList}
	 */
	this.navigation = new DropdownList(this);
	this.navigation.setAltText(Locale.cameraNavigation);
	this.navigation.size.set(100, 30);
	this.navigation.position.set(40, 5);
	this.navigation.updatePosition(Component.BOTTOM_RIGHT);
	this.navigation.updateSize();
	this.navigation.addValue(Locale.firstPerson, Settings.FIRST_PERSON);
	this.navigation.addValue(Locale.orbit, Settings.ORBIT);
	this.navigation.addValue(Locale.left, Settings.PLANAR_LEFT);
	this.navigation.addValue(Locale.right, Settings.PLANAR_RIGHT);
	this.navigation.addValue(Locale.front, Settings.PLANAR_FRONT);
	this.navigation.addValue(Locale.back, Settings.PLANAR_BACK);
	this.navigation.addValue(Locale.top, Settings.PLANAR_TOP);
	this.navigation.addValue(Locale.bottom, Settings.PLANAR_BOTTOM);
	this.navigation.element.style.opacity = 0.5;
	this.navigation.setOnChange(function()
	{
		Editor.settings.editor.navigation = self.navigation.getValue();
		self.updateCameraControls(Editor.settings.editor.navigation);
	});
	this.navigation.element.onmouseenter = function()
	{
		this.style.opacity = 1.0;
	};
	this.navigation.element.onmouseleave = function()
	{
		this.style.opacity = 0.5;
	};

	/**
	 * Button to toggle snap to grid functionality
	 *
	 * @method snapGridButton
	 * @type {ButtonIcon}
	 */
	this.snapGridButton = new ButtonIcon(this);
	this.snapGridButton.position.set(5, 40);
	this.snapGridButton.size.set(30, 30);
	this.snapGridButton.setImage(Global.FILE_PATH + "icons/misc/" + (Editor.settings.editor.snap ? "grid" : "freemove") + ".png");
	this.snapGridButton.setAltText(Locale.toggleSnapToGrid);
	this.snapGridButton.setImageScale(0.8, 0.8);
	this.snapGridButton.updateSize();
	this.snapGridButton.updatePosition(Component.BOTTOM_RIGHT);
	this.snapGridButton.element.style.borderRadius = "5px";
	this.snapGridButton.updateSyles({backgroundColor: "var(--panel-color)", opacity: 0.5}, {backgroundColor: "var(--panel-color)", opacity: 1.0});
	this.snapGridButton.setOnClick(function()
	{
		Editor.settings.editor.snap = !Editor.settings.editor.snap;
		self.transform.snap = Editor.settings.editor.snap;

		self.snapGridButton.setImage(Global.FILE_PATH + "icons/misc/" + (Editor.settings.editor.snap ? "grid" : "freemove") + ".png");
	});

	/**
	 * Button to toggle the camera mode between ORTHOGRAPHIC and PERSPECTIVE.
	 *
	 * @method cameraButton
	 * @type {ButtonIcon}
	 */
	this.cameraButton = new ButtonIcon(this);
	this.cameraButton.position.set(5, 5);
	this.cameraButton.size.set(30, 30);
	this.cameraButton.setImage(Global.FILE_PATH + "icons/misc/3d.png");
	this.cameraButton.setAltText(Locale.cameraMode);
	this.cameraButton.setImageScale(0.8, 0.8);
	this.cameraButton.updateSize();
	this.cameraButton.updatePosition(Component.BOTTOM_RIGHT);
	this.cameraButton.updateSyles({backgroundColor: "var(--panel-color)", opacity: 0.5}, {backgroundColor: "var(--panel-color)", opacity: 1.0});
	this.cameraButton.element.style.borderRadius = "5px";
	this.cameraButton.setOnClick(function()
	{
		self.setCameraMode();

		if (self.cameraMode === SceneEditor.ORTHOGRAPHIC)
		{
			self.cameraButton.setImage(Global.FILE_PATH + "icons/misc/2d.png");
		}
		else if (self.cameraMode === SceneEditor.PERSPECTIVE)
		{
			self.cameraButton.setImage(Global.FILE_PATH + "icons/misc/3d.png");
		}
	});

	/**
	 * The editor tool bar is used to select tool used to manipulate objects.
	 *
	 * @attribute toolBar
	 * @type {ToolBar}
	 */
	this.toolBar = new ToolBar(this);
	this.toolBar.setMode(Component.BOTTOM_LEFT);

	/**
	 * Event manager to handley keyboard shortcuts.
	 *
	 * @attribute manager
	 * @type {EventManager}
	 */
	this.manager = new EventManager();
	this.manager.add(document.body, "keydown", function(event)
	{
		var key = event.keyCode;

		if (event.ctrlKey)
		{
			if (self.container.focused)
			{
				if (key === Keyboard.NUM1)
				{
					self.selectTool(SceneEditor.SELECT);
				}
				else if (key === Keyboard.NUM2)
				{
					self.selectTool(SceneEditor.MOVE);
				}
				else if (key === Keyboard.NUM3)
				{
					self.selectTool(SceneEditor.SCALE);
				}
                                else if (key === Keyboard.NUM4)
                                {
                                        self.selectTool(SceneEditor.ROTATE);
                                }
                                else if (key === Keyboard.NUM5)
                                {
                                        self.selectTool(SceneEditor.MEASURE);
                                }
				else if (key === Keyboard.F)
				{
					self.focusObject();
				}
				else if (key === Keyboard.C)
				{
					Editor.copyObject();
				}
				else if (key === Keyboard.V)
				{
					Editor.pasteObject();
				}
				else if (key === Keyboard.X)
				{
					Editor.cutObject();
				}
			}
		}
	});

	this.canvas.resetCanvas();
}

SceneEditor.ORTHOGRAPHIC = 20;
SceneEditor.PERSPECTIVE = 21;

SceneEditor.SELECT = 0;
SceneEditor.MOVE = 100;
SceneEditor.SCALE = 101;
SceneEditor.ROTATE = 102;
SceneEditor.MEASURE = 103;

SceneEditor.prototype = Object.create(TabComponent.prototype);

SceneEditor.prototype.createRenderer = RendererCanvas.prototype.createRenderer;
SceneEditor.prototype.reloadContext = RendererCanvas.prototype.reloadContext;
SceneEditor.prototype.forceContextLoss = RendererCanvas.prototype.forceContextLoss;

SceneEditor.prototype.updateMetadata = function()
{
	if (this.scene !== null)
	{
		this.setName(this.scene.name);

		// Check if object has a parent
		if (this.scene.parent === null)
		{
			this.close();
			return;
		}

		// Check if object exists in parent
		var children = this.scene.parent.children;
		for (var i = 0; i < children.length; i++)
		{
			if (this.scene.uuid === children[i].uuid)
			{
				return;
			}
		}

		// If not found close tab
		if (i >= children.length)
		{
			this.close();
		}
	}
};

SceneEditor.prototype.activate = function()
{
	TabComponent.prototype.activate.call(this);

	this.canvas.createRenderer();
	this.updateSettings();

	this.mouse.setLock(false);

	this.mouse.create();
	this.manager.create();

	this.selectTool(SceneEditor.SELECT);
};

SceneEditor.prototype.deactivate = function()
{
	TabComponent.prototype.deactivate.call(this);

	this.mouse.dispose();
	this.manager.destroy();
};

/**
 * Update camera controller object.
 * 
 * Select a new controls object based on the mode passed as argument and attach the editor camera to it.
 *
 * @method updateCameraControls
 * @param {number} mode Camera mode.
 */
SceneEditor.prototype.updateCameraControls = function(mode)
{
	if (this.controlsMode === mode)
	{
		return;
	}
	
	this.controlsMode = mode;

	if (mode === Settings.FIRST_PERSON)
	{
		this.controls = new EditorFreeControls();
	}
	else if (mode === Settings.ORBIT)
	{
		this.controls = new EditorOrbitControls();
	}
	else
	{
		this.controls = new EditorPlanarControls(mode);
	}

	this.controls.attach(this.camera);
};

SceneEditor.prototype.updateSettings = function()
{
	// Grid
	this.gridHelper.visible = Editor.settings.editor.gridEnabled;
	this.gridHelper.setSize(Editor.settings.editor.gridSize);
	this.gridHelper.setSpacing(Editor.settings.editor.gridSpacing);
	this.gridHelper.update();

	// Axis
	this.axisHelper.visible = Editor.settings.editor.axisEnabled;

	// Orientation
	var size = Editor.settings.editor.cameraRotationCubeSize;
	this.orientation.viewport.size.set(size, size);

	// Controls
	this.navigation.setValue(Editor.settings.editor.navigation);
	this.updateCameraControls(Editor.settings.editor.navigation);

	// Tool
	this.transformationSpace.setValue(Editor.settings.editor.transformationSpace);
	this.transform.space = Editor.settings.editor.transformationSpace;
	this.transform.snap = Editor.settings.editor.snap;
	this.transform.translationSnap = Editor.settings.editor.gridSpacing;
	this.transform.rotationSnap = Editor.settings.editor.snapAngle;
};

SceneEditor.prototype.destroy = function()
{
	TabComponent.prototype.destroy.call(this);

	this.mouse.dispose();
	this.keyboard.dispose();
	this.transform.dispose();

	this.mouse.setLock(false);

	this.canvas.forceContextLoss();
};

SceneEditor.prototype.attach = function(scene)
{
	this.scene = scene;
	this.updateMetadata();

	if (this.camera !== null)
	{
		this.scene.defaultCamera = this.camera;
	}
};

/**
 * Check if a scene or object is attached to the editor.
 *
 * @method isAttached
 * @param {Scene} scene Scene to verify if is attached to this tab.
 */
SceneEditor.prototype.isAttached = function(scene)
{
	return this.scene === scene;
};

/**
 * Focus the first currently selected object, if there is one.
 *
 * @method focusObject
 */
SceneEditor.prototype.focusObject = function()
{
	if (Editor.selection.length > 0 && Editor.selection[0].isObject3D === true)
	{
		this.controls.focusObject(Editor.selection[0]);
	}
	else
	{
		Editor.alert(Locale.selectObjectFirst);
	}
};

/**
 * Update scene editor logic.
 *
 * @method update
 */
SceneEditor.prototype.update = function()
{
	this.mouse.update();
	this.keyboard.update();

	var isEditingObject = this.transform.update();

	// Check if mouse is inside canvas
        if (this.mouse.insideCanvas())
        {
                if (this.mode === SceneEditor.MEASURE)
                {
                        this.measurementPlane.constant = -this.gridHelper.position.y;
                        this.updateRaycasterFromMouse();

                        var hoverPoint = this.getMeasurementPoint();

                        if (this.mouse.buttonJustPressed(Mouse.RIGHT))
                        {
                                this.measurementPoints.length = 0;
                        	this.measurementPreview.visible = false;
                        }

                        if (this.measurementPoints.length === 1 && hoverPoint !== null)
                        {
                                this.updateMeasurementPreview(this.measurementPoints[0], hoverPoint);
                        }
                        else
                        {
                        	this.measurementPreview.visible = false;
                        }

                        if (hoverPoint !== null && this.mouse.buttonJustPressed(Mouse.LEFT))
                        {
                                this.measurementPoints.push(hoverPoint.clone());

                                if (this.measurementPoints.length === 2)
                                {
                                        this.createMeasurement(this.measurementPoints[0], this.measurementPoints[1]);
                                        this.measurementPoints.length = 0;
                                	this.measurementPreview.visible = false;
                                }
                        }

                        if (this.mouse.buttonDoubleClicked(Mouse.LEFT))
                        {
                                this.selectObjectWithMouse();
                        }
                }
                else if (this.mode === SceneEditor.SELECT)
                {
                        if (this.mouse.buttonJustPressed(Mouse.LEFT))
                        {
                                this.selectObjectWithMouse();
                        }
                }
                else
                {
                        // If mouse double clicked select object
                        if (this.mouse.buttonDoubleClicked(Mouse.LEFT))
                        {
                                this.selectObjectWithMouse();
                        }
                }

                // Lock mouse when camera is moving
                if (Editor.settings.editor.lockMouse && Nunu.runningOnDesktop())
                {
                        if (!isEditingObject && (this.mouse.buttonJustPressed(Mouse.LEFT) || this.mouse.buttonJustPressed(Mouse.RIGHT) || this.mouse.buttonJustPressed(Mouse.MIDDLE)))
			{
				this.mouse.setLock(true);
			}
			else if (this.mouse.buttonJustReleased(Mouse.LEFT) || this.mouse.buttonJustReleased(Mouse.RIGHT) || this.mouse.buttonJustReleased(Mouse.MIDDLE))
			{
				this.mouse.setLock(false);
			}
		}

		if (isEditingObject)
		{
			Editor.gui.inspector.updateValues();
		}
		else
		{
			// Update controls
			this.controls.update(this.mouse, this.keyboard);

			// Update grid helper position
			this.gridHelper.position.x = this.controls.position.x - this.controls.position.x % Editor.settings.editor.gridSpacing;
			this.gridHelper.position.z = this.controls.position.z - this.controls.position.z % Editor.settings.editor.gridSpacing;
		}
	}

	// If has objects selected
	if (Editor.hasObjectSelected())
	{
		// Update object transformation matrix
		for (var i = 0; i < Editor.selection.length; i++)
		{
			if (Editor.selection[i].matrixAutoUpdate === false)
			{
				Editor.selection[i].updateMatrix();
			}
		}
		
		// Update object helper
		this.objectHelper.traverse(function(children)
		{
			children.update();	
		});
	}

        this.render();

};

SceneEditor.prototype.updateMeasurementPreview = function(start, end)
{
        var position = this.measurementPreview.geometry.attributes.position;
        position.setXYZ(0, start.x, start.y, start.z);
        position.setXYZ(1, end.x, end.y, end.z);
        position.needsUpdate = true;
        this.measurementPreview.geometry.computeBoundingSphere();
        this.measurementPreview.visible = true;
};

SceneEditor.prototype.getMeasurementPoint = function()
{
        if (this.scene === null)
        {
                return null;
        }

        var intersects = this.raycaster.intersectObjects(this.scene.children, true);

        if (intersects.length > 0)
        {
                return intersects[0].point.clone();
        }

        if (this.raycaster.ray.intersectPlane(this.measurementPlane, this.measurementIntersection) !== null)
        {
                return this.measurementIntersection.clone();
        }

        return null;
};

SceneEditor.prototype.createMeasurement = function(start, end)
{
        if (this.scene === null)
        {
                return;
        }

        var measurement = new Measurement();
        measurement.setPoints(start, end);
        Editor.addObject(measurement, this.scene);
};

/**
 * Render all the editor scenes to the canvas using the renderer.
 *
 * Draws the attached scene/object after that it renders the helpers and tool scenes, the overlay orientation cube and then the camera previews.
 * 
 * @method render
 */
SceneEditor.prototype.render = function()
{
	if (this.canvas.renderer === null)
	{
		console.warn("nunuStudio: SceneEditor renderer is null.", this);
		return;
	}

	var width = this.canvas.resolution.x;
	var height = this.canvas.resolution.y;
	var canvas = this.canvas.canvas;
	var renderer = this.canvas.renderer;

	renderer.autoClear = false;
	renderer.setViewport(0, 0, width, height);
	renderer.setScissor(0, 0, width, height);

	// Clear with scene background
	renderer.setClearColor(this.scene.background);
	renderer.clear(true, true, true);

	// Render scene
	renderer.render(this.scene, this.camera);

	if (this.canvas.cssRenderer !== null)
	{
		this.canvas.cssRenderer.render(this.scene, this.camera);
	}

	renderer.render(this.helperScene, this.camera);
	renderer.render(this.toolScene, this.camera);

	// Draw camera cube
	if (Editor.settings.editor.cameraRotationCube)
	{
		var code = this.orientation.raycast(this.mouse, canvas);
		
		if (code !== null && (this.mouse.buttonDoubleClicked(Mouse.LEFT) || this.mouse.buttonJustPressed(Mouse.MIDDLE)))
		{
			this.controls.setOrientation(code);
		}

		renderer.clear(false, true, false);
		this.orientation.updateRotation(this.controls);
		this.orientation.render(renderer, canvas);
	}

	// Camera preview
	if (Editor.settings.editor.cameraPreviewEnabled)
	{
		renderer.setScissorTest(true);

		var previewRatio = 16.0 / 9.0;

		var viewport = new Viewport();
		viewport.width = width;
		viewport.height = height;
		viewport.offset = new Vector2(10, 10);
		viewport.size = new Vector2(Editor.settings.editor.cameraPreviewSize * previewRatio, Editor.settings.editor.cameraPreviewSize);
		viewport.anchor = Editor.settings.editor.cameraPreviewPosition;
		viewport.mode = Viewport.ABSOLUTE;
		viewport.update();
		viewport.enable(renderer);

		// Preview camera
		if (Editor.selection[0] instanceof PerspectiveCamera || Editor.selection[0] instanceof OrthographicCamera)
		{
			renderer.clear(true, true, true);

			var camera = Editor.selection[0];
			camera.resize(width, height, viewport);
			camera.setupRenderer(renderer);
			camera.render(renderer, this.scene);
		}
		// Preview cube camera
		else if (Editor.selection[0] instanceof CubeCamera)
		{
			var cameras = Editor.selection[0].cameras;
			var self = this;

			function renderCamera(index, x, y, w, h)
			{
				renderer.setViewport(x, y, w, h);
				renderer.setScissor(x, y, w, h);
				renderer.clear(true, true, true);

				cameras[index].updateMatrixWorld();
				cameras[index].render(renderer, self.scene);
			}

			// Change viewport to 4:3 ratio
			viewport.size = new Vector2(Editor.settings.editor.cameraPreviewSize * (4.0 / 3.0), Editor.settings.editor.cameraPreviewSize);;
			viewport.update();

			var size = viewport.viewport.w / 3;
			var x = viewport.viewport.x;
			var y = viewport.viewport.y;

			renderCamera(CubeTexture.LEFT, x, y + size, size, size);
			renderCamera(CubeTexture.FRONT, x + size, y + size, size, size);
			renderCamera(CubeTexture.RIGHT, x + size * 2, y + size, size, size);
			renderCamera(CubeTexture.BACK, x + size * 3, y + size, size, size);
			renderCamera(CubeTexture.TOP, x + size, y, size, size);
			renderCamera(CubeTexture.BOTTOM, x + size, y + size * 2, size, size);
		}
		// Preview all cameras in use
		else if (this.scene.cameras !== undefined && this.scene.cameras.length > 0)
		{
			renderer.clear(true, true, true);

			for (var i = 0; i < this.scene.cameras.length; i++)
			{
				var camera = this.scene.cameras[i];
				camera.resize(width, height, viewport);
				camera.setupRenderer(renderer);
				camera.render(renderer, this.scene);
			}
		}
	}

	renderer.setScissorTest(false);
};

/**
 * Update raycaster position from editor mouse position.
 *
 * @method updateRaycasterFromMouse
 */
SceneEditor.prototype.updateRaycasterFromMouse = function()
{
	this.normalized.set(this.mouse.position.x / this.canvas.size.x * 2 - 1, -(this.mouse.position.y / this.canvas.size.y) * 2 + 1);
	this.raycaster.setFromCamera(this.normalized, this.camera);
};

/**
 * Select objects mouse based on the mouse position.
 *
 * @method selectObjectWithMouse
 */
SceneEditor.prototype.selectObjectWithMouse = function()
{
	this.updateRaycasterFromMouse();

	var intersects = this.raycaster.intersectObjects(this.scene.children, true);

	if (intersects.length > 0)
	{	
		if (this.keyboard.keyPressed(Keyboard.CTRL))
		{	
			if (Editor.isSelected(intersects[0].object))
			{
				Editor.unselectObject(intersects[0].object);
			}
			else
			{
				Editor.addToSelection(intersects[0].object);
			}
		}
		else
		{
			Editor.selectObject(intersects[0].object);
		}
	}
};

/**
 * Update raycaster with new x and y positions (normalized -1 to 1).
 *
 * @method updateRaycaster
 * @param {number} x
 * @param {number} y
 */
SceneEditor.prototype.updateRaycaster = function(x, y)
{
	this.normalized.set(x, y);
	this.raycaster.setFromCamera(this.normalized, this.camera);
};

/**
 * Set the editor camera projection mode (ortographic or perspective).
 *
 * @method setCameraMode
 * @param {number} mode
 */
SceneEditor.prototype.setCameraMode = function(mode)
{
	if (mode === this.cameraMode)
	{
		return;
	}

	if (mode === undefined)
	{
		mode = this.cameraMode === SceneEditor.PERSPECTIVE ? SceneEditor.ORTHOGRAPHIC : SceneEditor.PERSPECTIVE;
	}
	
	this.cameraMode = mode;

	var aspect = this.canvas !== null ? this.canvas.size.x / this.canvas.size.y : 1.0;

	if (this.cameraMode === SceneEditor.ORTHOGRAPHIC)
	{
		this.camera = new OrthographicCamera(10, aspect, OrthographicCamera.RESIZE_HORIZONTAL);
	}
	else if (this.cameraMode === SceneEditor.PERSPECTIVE)
	{
		this.camera = new PerspectiveCamera(60, aspect);
	}

	if (this.scene !== null)
	{
		this.scene.defaultCamera = this.camera;
	}

	this.transform.camera = this.camera;

	if (this.controls !== null)
	{
		this.controls.attach(this.camera);
		this.controls.reset();
	}
};

/**
 * Select transform tool, possible values are:
 * - SceneEditor.MOVE
 * - SceneEditor.SCALE
 * - SceneEditor.ROTATE
 *
 * @param selectTool
 * @param {number} tool Tool to select.
 */
SceneEditor.prototype.selectTool = function(tool)
{
        var previousMode = this.mode;

        if (tool !== undefined)
        {
                this.mode = tool;
        }

        if (this.mode === SceneEditor.MOVE)
        {
                this.transform.setMode(TransformControls.TRANSLATE);
                this.transform.space = Editor.settings.editor.transformationSpace;
        }
        else if (this.mode === SceneEditor.SCALE)
        {
                this.transform.setMode(TransformControls.SCALE);
        }
        else if (this.mode === SceneEditor.ROTATE)
        {
                this.transform.setMode(TransformControls.ROTATE);
                this.transform.space = Editor.settings.editor.transformationSpace;
        }
        else if (this.mode === SceneEditor.SELECT)
        {
                this.transform.setMode(TransformControls.NONE);
        }
        else if (this.mode === SceneEditor.MEASURE)
        {
                this.transform.setMode(TransformControls.NONE);
                this.measurementPoints.length = 0;
                this.measurementPreview.visible = false;

                if (tool === SceneEditor.MEASURE)
                {
                        Editor.alert(Locale.selectMeasurementPoints);
                }
        }
        else
        {
                this.measurementPoints.length = 0;
                this.measurementPreview.visible = false;
        }

        if (previousMode === SceneEditor.MEASURE && this.mode !== SceneEditor.MEASURE)
        {
                this.measurementPoints.length = 0;
                this.measurementPreview.visible = false;
        }

        this.toolBar.selectTool(this.mode);
};

/**
 * Update the selection status of the tab.
 *
 * Select the adequate helper to debug selected objects and attach the objects to the transform tools.
 *
 * @method updateSelection
 */
SceneEditor.prototype.updateSelection = function()
{
	// Filter Object3D objects only (to exclude resources)
	var selectedObjects = [];
	for (var i = 0; i < Editor.selection.length; i++)
	{
		if (Editor.selection[i].isObject3D === true)
		{
			selectedObjects.push(Editor.selection[i]);
		}
	}

	this.transform.attach(selectedObjects);
	this.objectHelper.removeAll();

	for (var i = 0; i < selectedObjects.length; i++)
	{
		var object = selectedObjects[i];

		// Camera
		if (object instanceof Camera)
		{
			this.objectHelper.add(new CameraHelper(object));
			this.objectHelper.add(new ObjectIconHelper(object, Global.FILE_PATH + "icons/camera/camera.png"));
		}
		// Light
		else if (object instanceof Light)
		{
			// Directional light
			if (object instanceof DirectionalLight)
			{
				this.objectHelper.add(new DirectionalLightHelper(object, 1));
			}
			// Light probe
			else if (object instanceof LightProbe)
			{
				this.objectHelper.add(new LightProbeHelper(object, 2));
			}
			// Point light
			else if (object instanceof PointLight)
			{
				this.objectHelper.add(new PointLightHelper(object, 1));
			}
			// RectArea light
			else if (object instanceof RectAreaLight)
			{
				this.objectHelper.add(new RectAreaLightHelper(object));
			}
			// Spot light
			else if (object instanceof SpotLight)
			{
				this.objectHelper.add(new SpotLightHelper(object));
			}
			// Hemisphere light
			else if (object instanceof HemisphereLight)
			{
				this.objectHelper.add(new HemisphereLightHelper(object, 1));
			}
			// Ambient light
			else
			{
				this.objectHelper.add(new ObjectIconHelper(object, ObjectIcons.get(object.type)));
			}
		}
		// Physics
		else if (object instanceof PhysicsObject)
		{
			this.objectHelper.add(new PhysicsObjectHelper(object));
		}
		// LensFlare
		else if (object instanceof LensFlare)
		{
			this.objectHelper.add(new ObjectIconHelper(object, ObjectIcons.get(object.type)));
		}
		// Skinned Mesh
		else if (object instanceof SkinnedMesh)
		{
			this.objectHelper.add(new SkeletonHelper(object.parent));
			this.objectHelper.add(new WireframeHelper(object, 0xFFFF00));
		}
		// Bone
		else if (object instanceof Bone)
		{
			this.objectHelper.add(new SkeletonHelper(object.parent));
			this.objectHelper.add(new ObjectIconHelper(object, ObjectIcons.get(object.type)));
		}
		// Mesh
		else if (object instanceof Mesh)
		{
			this.objectHelper.add(new WireframeHelper(object, 0xFFFF00));
		}
		// Line
		else if (object instanceof Line)
		{
			this.objectHelper.add(new LineHelper(object, 0xFFFF00));
		}
		// Points
		else if (object instanceof Points)
		{
			this.objectHelper.add(new PointsHelper(object, 0xFFFF00));
		}
		// Spine animation
		else if (object instanceof SpineAnimation)
		{
			this.objectHelper.add(new ObjectIconHelper(object, ObjectIcons.get(object.type)));
		}
		// Group
		else if (object instanceof Group)
		{
			this.objectHelper.add(new BoxHelper(object, 0xFFFF00));
			this.objectHelper.add(new ObjectIconHelper(object, ObjectIcons.get(object.type)));
		}
		// Object 3D
		else
		{
			this.objectHelper.add(new ObjectIconHelper(object, ObjectIcons.get(object.type)));
		}
	}
};

SceneEditor.prototype.updateVisibility = function()
{
	TabComponent.prototype.updateVisibility.call(this);
};

SceneEditor.prototype.updateSize = function()
{
	TabComponent.prototype.updateSize.call(this);

	this.sideBar.position.set(0, 0);
	this.sideBar.size.set(40, this.size.y);
	this.sideBar.updateInterface();

	this.toolBar.position.set(this.size.x / 2 - this.toolBar.size.x / 2, 5);
	this.toolBar.updateInterface();

	var width = this.size.x - this.sideBar.size.x;
	var height = this.size.y;

	this.canvas.position.set(this.sideBar.size.x, 0);
	this.canvas.size.set(width, height);
	this.canvas.updateInterface();

	if (this.camera !== null)
	{
		this.camera.aspect = width / height;
		this.camera.updateProjectionMatrix();
	}
};

export {SceneEditor};
